﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HgRemoteContracts;
using HgRemoteServer.Config;
using System.Configuration;
using System.ServiceModel;
using System.IO;

namespace HgRemoteServer
{
	public class HgServer : IHgServer
	{
		ServerConfig config;

		public HgServer()
		{
			config = ConfigurationManager.GetSection("ServerConfig") as ServerConfig;
		}

		private Repository GetRepo(string repoName)
		{
			Repository repo = config.Repositories.FirstOrDefault(r => String.Equals(r.Name, repoName, StringComparison.OrdinalIgnoreCase));
			if (repo == null)
				throw new FaultException("Unknown repository.");
			return repo;
		}

		public List<StatusItem> Status(string repoName)
		{
			Repository repo = GetRepo(repoName);

			return RepoCmd(repo, "status")
				.Where(l => l != null)
				.Select(line => StatusItem.Read(line))
				.ToList();
		}

		public List<HgRepo> GetRepositories()
		{
			return config.Repositories
				.Select(r => r.ToWcfRepo())
				.OrderBy(r => r.Name)
				.ToList();
		}

		public List<StatusItem> Commit(string repoName, HgUser user, string message, string[] files)
		{
			Repository repo = GetRepo(repoName);

			string msgPath = Path.GetTempFileName();
			File.WriteAllText(msgPath, message, Encoding.Default);

			try
			{
				string args = String.Format("commit --user \"{0} <{1}>\" --logfile \"{2}\"", user.Name, user.Email, msgPath);

				if (files != null)
					args += String.Join(String.Empty, files.Select(f => " \"" + f + "\"").ToArray());

				RepoCmd(repo, args);
			}
			finally
			{
				File.Delete(msgPath);
			}

			return Status(repoName);
		}

		public List<string> Diff(string repoName, string path)
		{
			Repository repo = GetRepo(repoName);
			return RepoCmd(repo, "diff --git \"{0}\"", path);
		}

		public List<StatusItem> Add(string repoName, string[] paths)
		{
			Repository repo = GetRepo(repoName);
			if (paths.Length > 0)
			{
				string pathStr = String.Join(" ", paths.Select(p => "\"" + p + "\"").ToArray());
				RepoCmd(repo, "add {0}", pathStr);
			}
			return Status(repoName);
		}

		public List<StatusItem> Forget(string repoName, string[] paths)
		{
			Repository repo = GetRepo(repoName);
			if (paths.Length > 0)
			{
				string pathStr = String.Join(" ", paths.Select(p => "\"" + p + "\"").ToArray());
				RepoCmd(repo, "forget {0}", pathStr);
			}
			return Status(repoName);
		}

		public List<StatusItem> Revert(string repoName, bool backup, string[] paths)
		{
			Repository repo = GetRepo(repoName);
			if (paths.Length > 0)
			{
				string pathStr = String.Join(" ", paths.Select(p => "\"" + p + "\"").ToArray());
				RepoCmd(repo, "revert {0}{1}", backup ? "" : "--no-backup ", pathStr);
			}
			return Status(repoName);
		}

		public List<StatusItem> DeleteUnversioned(string repoName, string[] paths)
		{
			Repository repo = GetRepo(repoName);

			FileInfo[] files = paths
				.Select(path => Path.Combine(repo.RootPath, path))
				.Select(fullPath => new FileInfo(fullPath))
				.ToArray();

			if (files.All(fi => fi.Exists))
			{
				foreach (var fi in files)
					fi.Delete();
			}
			else
				throw new FaultException("Files in inconsistent state.  Try refreshing.");
			return Status(repoName);
		}

		public VisualDiffInfo VisualDiff(string repoName, string path)
		{
			Repository repo = GetRepo(repoName);
			string fullPath = Path.Combine(repo.RootPath, path);

			VisualDiffInfo vdi = new VisualDiffInfo();
			if (File.Exists(fullPath))
				vdi.Right = File.ReadAllBytes(fullPath);

			var statusCheck = RepoCmd(repo, "status \"{0}\"", path);
			if (statusCheck.Count > 0 && statusCheck[0].Length > 0)
			{
				FileStatus status = StatusItem.FromStatusChar(statusCheck[0][0]);

				if (status == FileStatus.Modified)
				{
					string tempPath = Path.GetTempFileName();
					var results = RepoCmd(repo, "cat --output \"{0}\" \"{1}\"", tempPath, path);
					vdi.Left = File.ReadAllBytes(tempPath);
					File.Delete(tempPath);
				}
			}

			return vdi;
		}

		public List<StatusItem> Rollback(string repoName)
		{
			Repository repo = GetRepo(repoName);
			List<string> results = RepoCmd(repo, "rollback");
			return Status(repoName);
		}

		private List<string> RepoCmd(Repository repo, string hgArgsFormat, params object[] args)
		{
			CmdRunner cmd = new CmdRunner("hg", String.Format(hgArgsFormat, args), repo.RootPath);
			return cmd.GetOutputLines();
		}

		public void RunTask(string repoName, string taskName)
		{
			Repository repo = GetRepo(repoName);
			RepositoryTask task = repo.Tasks.FirstOrDefault(t => t.Name == taskName);

			if (task == null)
				throw new FaultException("Unknown task.");

			string wd = task.StartIn ?? Path.GetDirectoryName(task.Executable);
			CmdStreamer cmd = new CmdStreamer(task.Executable, task.Arguments, wd);
			cmd.Run();
		}

		public void Incoming(string repoName)
		{
			Repository repo = GetRepo(repoName);
			CmdStreamer cmd = new CmdStreamer("hg", "incoming", repo.RootPath);
			cmd.Run();
		}

		public void Outgoing(string repoName)
		{
			Repository repo = GetRepo(repoName);
			CmdStreamer cmd = new CmdStreamer("hg", "outgoing", repo.RootPath);
			cmd.Run();
		}

		public void Fetch(string repoName, HgUser user)
		{
			Repository repo = GetRepo(repoName);

			string args = String.Format("fetch -m merge -u \"{0} <{1}>\"", user.Name, user.Email);
			CmdStreamer cmd = new CmdStreamer("hg", args, repo.RootPath);
			// Limit mergeing to the effortless kind only
			cmd.SetEnvironmentVar("HGMERGE", "internal:merge");
			cmd.EvalStatusLine += (s, e) =>
			{
				if (e.ResultLine != null && e.ResultLine.StartsWith("use 'hg resolve' to retry"))
				{
					cmd.AddCommand("hg", "update -C", repo.RootPath);
					cmd.AddCommand("hg", "rollback", repo.RootPath);
					cmd.AddCommand("hg", "revert --all", repo.RootPath);
					cmd.AddFinishMessage("Automatic merge failed.");
				}
			};
			cmd.Run();
		}

		public void Push(string repoName)
		{
			Repository repo = GetRepo(repoName);
			CmdStreamer cmd = new CmdStreamer("hg", "push", repo.RootPath);
			cmd.Run();
		}

	}
}
